1. 函数高级

此部分作为对函数知识的补充,逻辑性比较差,行业比较乱。

1.1. 变量作用域

变量的作用域指的是变量的作何用范围。 每个变量有相应的作用范围限制,超出 作用范围的变量是不允许的。

变量按照作用域可以分类为:

  • 全局(global): 定义在函数外部,整个程序内都可以使用

  • 局部(local):在函数代码内部定义,超出某一段代码范围后就不能使用

变量的作用范围可以粗略的分为以下几种:

  • 全局变量:在整个全局范围都有效

  • 全局变量在局部可以使用(即函数内部可以方位函数外部定义的变量)

  • 局部变量在局部范围可以使用

  • 局部变量在全局范围无法使用

1.1.1. LEGB原则

Python中可以把变量的作用域概括成LEGB:

  • L(Local: 局部作用域

  • E(Enclosing function locale):外部嵌套函数作用域

  • G(Global module):函数定义所在模块作用域

  • B(Buildin): Python内置模块的作用域

参见以下案例:

  # 认为a1是全局的
  a1 = 100

  def fun():
      print(a1)
      print("I am in fun")
      # a2的作用范围是fun
      a2 = 99
      print(a2)
      
      
  print(a1)
  fun()
  print(a2)

我们在函数外部定义了变量a1, 函数内部对这个变量进行了打印操作,这样是可以的,函数内部可以直接使用 函数外部的变量, 同时函数内定义了变量a2, 定义后再函数内使用这个变量没有问题,但我们在外部使用这个变量 就会报错,参加一下运行结果。

函数内部使用函数外部定义的变量,在上面的情况只限于对变量进行读操作,不能有赋值等修改操作

代码运行结果如下:

  100
  100
  I am in fun
  99

  ---------------------------------------------------------------------------
  NameError                                 Traceback (most recent call last)
  <ipython-input-4-36e1efbb7d66> in <module>()
       12 print(a1)
       13 fun()
  ---> 14 print(a2)

  NameError: name 'a2' is not defined

1.1.2. 提升局部变量为全局变量

如果我们试图在函数内修改函数外部定义的变量,此时需要使用关键字global还用来告诉程序 要对函数外部定义的变量进行修改,即进行全局声明, 此时也可以把global关键字理解成 是在函数内定义了一个作用域为全局的外部变量。

使用格式如下:

  def 函数名()
      #提升局部变量为全局变量
      global 局部变量
      其他操作...

参见以下案例:

  def fun():
      global b1
      b1 = 100
      print(b1)
      print("I am in fun")
      # a2的作用范围是fun
      b2 = 99
      print(b2)

  #print(b2) #变量b2的作用域是局部,此处引用报错
  fun()
  print(b1)

代码运行结果如下, 需要注意的是变量b1是在函数内部定义的, 只有当函数运行之后,即在函数 被调用后系统才会还有这个变量,在函数调用前使用这个变量会报错:

  100
  I am in fun
  99
  100

1.1.3. nonlocal-提升局部变量为非局部变量

nonlocal的作用是声明当前变量不是当前函数内部的变量,他有可能是当前函数的外部变量(不是全局变量)。

语法格式为:

  def 外部函数()
      局部变量
      def 内部函数():
          nonlocal 变量名 #声明当前变量不是内部函数中的变量
          其他代码...
      return 代码

如果在内部函数中只是仅仅读外部变量,可以不在此变量前加nonlocal 如果在内部函数中尝试进行修改外部变量,且外部变量为不可变类型,则需要在变量前加nonlocal,如果变量为可变类型,则不需要添加

1.2. 函数的几个特殊用法

1.2.1. 函数的嵌套

函数嵌套是指一个函数里用def关键字来创建其它函数的情况。

常见的格式如下:

def fun1():
    print("fun1被调用)
    def fun2():
         print("fun2被调用")
    fun2() #调用上面定义的函数
    print("fun1调用结束")

1.2.2. 函数名字作为变量赋值

函数名可以看做普通变量,他在创建时绑定一个函数,此时可以对另外变量用函数名称赋值,此时被赋值的变量 可以看做是这个函数的第二个名字。

参加案例如下:

def fn():
    print("hello world")
f1 = fn
f1()  # 等同于fn()

1.2.3. 函数作为函数的参数

函数可以当做变量赋值,也可以当做参数传入另外函数内部使用。

参考下面代码:

   def myadd(x, y):
      return x + y

  def mymul(x, y):
      return x * y

  def getfun(fx):
      if "add" == fx:
          return myadd
      elif "mul" == fx:
          return mymul
      return None

  a = int(input("请输入第一个数"))
  b = int(input("请输入第二个数"))
  op = int(input("请输入操作方法:(mul/add):"))
  f = getfun(op) 
  print(f(a,b)) #注意函数的调用方式

1.3. 递归函数

函数写完以后被调用才会运行。

函数代码里可以继续调用其他函数,但如果函数调用自己,则此类函数叫做递归函数,即函数直接或间接调用 自己。

递归函数的优点为简洁和理解容易, 还有同学会觉得很难理解,但你是不理解递归的定义导致的,在有些特定的场合 如果不用递归的话,直接写出来的程序是很复杂的。

递归函数的缺点是系统对递归深度有限制,而且递归函数消耗资源大。

Python对递归深度有限制,即函数调用自己的次数有限制, 超过限制报错

在编写递归函数时,一定要注意递归函数的结束条件,递归函数不能无限制递归调用下去,否则会好近计算机所有资源。

下面案例是一个失败的递归调用案例,因为没有对结束条件进行规定导致递归函数一直运行:

# 递归调用深度限制代码
x = 0
def fun():
  global x
  x += 1
  print(x)
  # 函数自己调用自己
  fun()

# 调用函数
fun()

函数运行结果如下, 注意最后的报错,是递归错误:

1
2
3
4
5
6
7
8
140
---------------------------------------------------------------------------
RecursionError                            Traceback (most recent call last)
<ipython-input-15-342ffb5916b5> in <module>()
     11 
     12 # 调用函数
---> 13 fun()
<ipython-input-15-342ffb5916b5> in fun()
      8     print(x)
      9     # 函数自己调用自己
---> 10     fun()
     11 
     12 # 调用函数
... last 1 frames repeated, from the frame below ...
<ipython-input-15-342ffb5916b5> in fun()
      8     print(x)
      9     # 函数自己调用自己
---> 10     fun()
     11 
     12 # 调用函数

RecursionError: maximum recursion depth exceeded in comparison

1.3.1. 斐波那契数列

递归函数一个经典的案例是斐波那契数列,参考一下代码:

# 斐波那契额数列
# 一列数字,第一个值是1, 第二个也是1, 从第三个开始,每一个数字的值等于前两个数字出现的值的和
# 数学公式为: f(1) = 1, f(2) = 1, f(n) = f(n-1) + f(n-2)
# 例如: 1,1,2,3,5,8,13.。。。。。。。。

# 下面求斐波那契数列函数有一定问题,比如n一开始就是负数,如何修正
# n表示求第n个数子的斐波那契数列的值
def fib(n):
  if n == 1:
      return 1
  
  if n == 2:
      return 1
  
  # 思考:为什么后面return能够正确执行,而不用else语句
  return fib(n-1) + fib(n-2)

print(fib(3))
print(fib(10))

代码运行结果如下:

2
55

1.4. 系统内置函数

Python有一些内置的函数,都封装在了__builtins__模块中,可以通过 print((dir(__builtins__))) 来打印出所有内置函数和功能。

下面列举几个常用的内置函数:

1.4.1. eval()函数

Python中,eval函数可以把一个字符串当成一个表达式来执行, 返回表达式执行后的结果作为函数执行 的结果。

函数的定义接口为eval(string_code, globals=None, locals=None), 其中:

  • string_code: 被当做命令执行的字符串

  • globals: 执行代码时候的全局变量,作为执行环境参数

  • locals: 执行代码时候的局部变量,作为执行环境参数

代码案例参考如下:

  x = 100
  y = 200
  # 执行x+y
  # z = x + y
  z1 = x + y
  z2 = eval("x+y")

  print(z1)
  print(z2)

运行结果为:

  300
  300

1.4.2. exec()函数

eval功能类似, 但是不返回结果.

函数的定义接口为exec(string_code, globals=None, locals=None)

代码案例参考如下:

  # exec示例
  x = 100
  y = 200
  # 执行x+y
  # z = x + y
  z1 = x + y
  # 1, 注意字符串中引号的写法
  # 2. 比对exec执行结果和代码执行结果
  z2 = exec("print('x+y:', x+y)")

  print(z1)
  print(z2)

运行结果为:

  x+y: 300
  300
  None

1.4.3. repr()函数

  • repr函数用来取得对象的规范字符串表示。

  • 反引号(也称转换符)可以完成相同的功能。

  • 注意,在大多数时候有eval(repr(object))==object。

  • 可以通过定义类__repr__方法来控制你的对象在被repr函数调用的时候返回内容

1.4.4. compile()函数

compile语句是从type类型中将str里面的语句创建成代码对象,以供以后执行。

  • type:

    • eval: 配合eval使用

    • single: 配合单一语句的exec使用

    • exec: 配合多语句的exec使用

  • file 是代码存放的地方,通常为空字符串

参看以下代码:

    >>> eval_code = compile( '1+2', '', 'eval')
    >>> eval_code
    <code object <module> at 0142ABF0, file "", line 1>
    >>> eval(eval_code)
    3
     
    >>> single_code = compile( 'print "www.baoshu.red"', '', 'single' )
    >>> single_code
    <code object <module> at 01C68848, file "", line 1>
    >>> exec(single_code)
    www.baoshu.red
     
    >>> exec_code = compile( """for i in range(5):
    ...   print "iter time: %d" % i""", '', 'exec' )
    >>> exec_code
    <code object <module> at 01C68968, file "", line 1>
    >>> exec(exec_code)
    iter time: 0
    iter time: 1
    iter time: 2
    iter time: 3
    iter time: 4

1.4.5. globals, locals函数

在代码中我们可以通过globals,locals两个函数显示出我们所有的局部变量和全局变量。

# globals 和 locals
# globals 和 locals 叫做内建函数
a = 1
b = 2

def fun(c,d):
    e = 111
    print("Locals={0}".format(locals()))
    print("Globals={0}".format(globals()))
    
fun(100, 200)

代码运行结果如下:

Locals={'e': 111, 'd': 200, 'c': 100}
Globals={'__name__': '__main__', '__doc__': 'Automatically created module for IPython interactive environment', '__package__': None, '__loader__': None, '__spec__': None, '__builtin__': <module 'builtins' (built-in)>, '__builtins__': <module 'builtins' (built-in)>, '_ih': ['', '# 认为a1是全局的\na1 = 100\n\ndef fun():\n    print(a1)\n    print("I am in fun")\n    # a2的作用范围是fun\n    a2 = 99\n    print(a2)\n    \n    \nprint(a1)\n#print(a2)', '# 认为a1是全局的\na1 = 100\n\ndef fun():\n    print(a1)\n    print("I am in fun")\n    # a2的作用范围是fun\n    a2 = 99\n    print(a2)\n    \n    \nprint(a1)\nfun()\n#print(a2)', '# 认为a1是全局的\na1 = 100\n\ndef fun():\n    print(a1)\n    print("I am in fun")\n    # a2的作用范围是fun\n    a2 = 99\n    print(a2)\n    \n    \nprint(a1)\nfun()\nprint(a2)', '# 认为a1是全局的\na1 = 100\n\ndef fun():\n    print(a1)\n    print("I am in fun")\n    # a2的作用范围是fun\n    a2 = 99\n    print(a2)\n    \n    \nprint(a1)\nfun()\nprint(a2)', '\ndef fun():\n    b1 = 100\n    print(b1)\n    print("I am in fun")\n    # a2的作用范围是fun\n    b2 = 99\n    print(b2)\n    \n    \nprint(b1)\nprint(b2)\nfun()', '\ndef fun():\n    global b1 = 100\n    print(b1)\n    print("I am in fun")\n    # a2的作用范围是fun\n    b2 = 99\n    print(b2)\n    \n    \nprint(b1)\n#print(b2)\nfun()', '\ndef fun():\n    global b1\n    b1 = 100\n    print(b1)\n    print("I am in fun")\n    # a2的作用范围是fun\n    b2 = 99\n    print(b2)\n    \n    \nprint(b1)\n#print(b2)\nfun()', '\ndef fun():\n    global b1\n    b1 = 100\n    print(b1)\n    print("I am in fun")\n    # a2的作用范围是fun\n    b2 = 99\n    print(b2)\n    \n    \nprint(b1)\n#print(b2)\nfun()', '\ndef fun():\n    b1 = 100\n    global b1\n    b1 = 100\n    print(b1)\n    print("I am in fun")\n    # a2的作用范围是fun\n    b2 = 99\n    print(b2)\n    \n    \nprint(b1)\n#print(b2)\nfun()', '# globals 和 locals\na = 1\nb = 2\n\ndef fun(c,d):\n    e = 111\n    print("Locals={0}".format(locals()))\n    print("Globals={0}".format(globals()))\n    \nfun(100, 200)'], '_oh': {}, '_dh': ['/home/tlxy/cookbook_and_code'], 'In': ['', '# 认为a1是全局的\na1 = 100\n\ndef fun():\n    print(a1)\n    print("I am in fun")\n    # a2的作用范围是fun\n    a2 = 99\n    print(a2)\n    \n    \nprint(a1)\n#print(a2)', '# 认为a1是全局的\na1 = 100\n\ndef fun():\n    print(a1)\n    print("I am in fun")\n    # a2的作用范围是fun\n    a2 = 99\n    print(a2)\n    \n    \nprint(a1)\nfun()\n#print(a2)', '# 认为a1是全局的\na1 = 100\n\ndef fun():\n    print(a1)\n    print("I am in fun")\n    # a2的作用范围是fun\n    a2 = 99\n    print(a2)\n    \n    \nprint(a1)\nfun()\nprint(a2)', '# 认为a1是全局的\na1 = 100\n\ndef fun():\n    print(a1)\n    print("I am in fun")\n    # a2的作用范围是fun\n    a2 = 99\n    print(a2)\n    \n    \nprint(a1)\nfun()\nprint(a2)', '\ndef fun():\n    b1 = 100\n    print(b1)\n    print("I am in fun")\n    # a2的作用范围是fun\n    b2 = 99\n    print(b2)\n    \n    \nprint(b1)\nprint(b2)\nfun()', '\ndef fun():\n    global b1 = 100\n    print(b1)\n    print("I am in fun")\n    # a2的作用范围是fun\n    b2 = 99\n    print(b2)\n    \n    \nprint(b1)\n#print(b2)\nfun()', '\ndef fun():\n    global b1\n    b1 = 100\n    print(b1)\n    print("I am in fun")\n    # a2的作用范围是fun\n    b2 = 99\n    print(b2)\n    \n    \nprint(b1)\n#print(b2)\nfun()', '\ndef fun():\n    global b1\n    b1 = 100\n    print(b1)\n    print("I am in fun")\n    # a2的作用范围是fun\n    b2 = 99\n    print(b2)\n    \n    \nprint(b1)\n#print(b2)\nfun()', '\ndef fun():\n    b1 = 100\n    global b1\n    b1 = 100\n    print(b1)\n    print("I am in fun")\n    # a2的作用范围是fun\n    b2 = 99\n    print(b2)\n    \n    \nprint(b1)\n#print(b2)\nfun()', '# globals 和 locals\na = 1\nb = 2\n\ndef fun(c,d):\n    e = 111\n    print("Locals={0}".format(locals()))\n    print("Globals={0}".format(globals()))\n    \nfun(100, 200)'], 'Out': {}, 'get_ipython': <bound method InteractiveShell.get_ipython of <ipykernel.zmqshell.ZMQInteractiveShell object at 0x7f891e18d198>>, 'exit': <IPython.core.autocall.ZMQExitAutocall object at 0x7f8914136fd0>, 'quit': <IPython.core.autocall.ZMQExitAutocall object at 0x7f8914136fd0>, '_': '', '__': '', '___': '', '_i': '\ndef fun():\n    b1 = 100\n    global b1\n    b1 = 100\n    print(b1)\n    print("I am in fun")\n    # a2的作用范围是fun\n    b2 = 99\n    print(b2)\n    \n    \nprint(b1)\n#print(b2)\nfun()', '_ii': '\ndef fun():\n    global b1\n    b1 = 100\n    print(b1)\n    print("I am in fun")\n    # a2的作用范围是fun\n    b2 = 99\n    print(b2)\n    \n    \nprint(b1)\n#print(b2)\nfun()', '_iii': '\ndef fun():\n    global b1\n    b1 = 100\n    print(b1)\n    print("I am in fun")\n    # a2的作用范围是fun\n    b2 = 99\n    print(b2)\n    \n    \nprint(b1)\n#print(b2)\nfun()', '_i1': '# 认为a1是全局的\na1 = 100\n\ndef fun():\n    print(a1)\n    print("I am in fun")\n    # a2的作用范围是fun\n    a2 = 99\n    print(a2)\n    \n    \nprint(a1)\n#print(a2)', 'a1': 100, 'fun': <function fun at 0x7f88fbfb5268>, '_i2': '# 认为a1是全局的\na1 = 100\n\ndef fun():\n    print(a1)\n    print("I am in fun")\n    # a2的作用范围是fun\n    a2 = 99\n    print(a2)\n    \n    \nprint(a1)\nfun()\n#print(a2)', '_i3': '# 认为a1是全局的\na1 = 100\n\ndef fun():\n    print(a1)\n    print("I am in fun")\n    # a2的作用范围是fun\n    a2 = 99\n    print(a2)\n    \n    \nprint(a1)\nfun()\nprint(a2)', '_i4': '# 认为a1是全局的\na1 = 100\n\ndef fun():\n    print(a1)\n    print("I am in fun")\n    # a2的作用范围是fun\n    a2 = 99\n    print(a2)\n    \n    \nprint(a1)\nfun()\nprint(a2)', '_i5': '\ndef fun():\n    b1 = 100\n    print(b1)\n    print("I am in fun")\n    # a2的作用范围是fun\n    b2 = 99\n    print(b2)\n    \n    \nprint(b1)\nprint(b2)\nfun()', '_i6': '\ndef fun():\n    global b1 = 100\n    print(b1)\n    print("I am in fun")\n    # a2的作用范围是fun\n    b2 = 99\n    print(b2)\n    \n    \nprint(b1)\n#print(b2)\nfun()', '_i7': '\ndef fun():\n    global b1\n    b1 = 100\n    print(b1)\n    print("I am in fun")\n    # a2的作用范围是fun\n    b2 = 99\n    print(b2)\n    \n    \nprint(b1)\n#print(b2)\nfun()', '_i8': '\ndef fun():\n    global b1\n    b1 = 100\n    print(b1)\n    print("I am in fun")\n    # a2的作用范围是fun\n    b2 = 99\n    print(b2)\n    \n    \nprint(b1)\n#print(b2)\nfun()', '_i9': '\ndef fun():\n    b1 = 100\n    global b1\n    b1 = 100\n    print(b1)\n    print("I am in fun")\n    # a2的作用范围是fun\n    b2 = 99\n    print(b2)\n    \n    \nprint(b1)\n#print(b2)\nfun()', '_i10': '# globals 和 locals\na = 1\nb = 2\n\ndef fun(c,d):\n    e = 111\n    print("Locals={0}".format(locals()))\n    print("Globals={0}".format(globals()))\n    \nfun(100, 200)', 'a': 1, 'b': 2}

1.5. 函数式编程(FunctionalProgramming)和高阶函数

函数式编程是基于lambda演算的一种编程方式, 在函函数式编程中程序只有函数,其中函数可以作为参数,同样可以作为返回值。

纯函数式编程语言有LISPHaskell等, 但Python中要讲的函数式编程只是借鉴函数式编程的一些特点,可以理解成一半函数式一半Python

1.5.1. lambda表达式

函数的作用是最大程度复用代码,但是也存在一些问题,比如如果函数的代码很少或很短:

  • 会造成程序很细碎,反而显得比较啰嗦。

  • 如果函数被调用次数少,则会造成资源浪费,比较每次调用函数需要一定的系统资源开销

  • 对于阅读者来说,频繁的跳转会造成阅读流程的被迫中断

对于一些比较短小的代码,我们可以尝试使用匿名函数来替代一个比较常规的函数的定义流程,匿名函数也叫lambda表达式:

  • 是一个表达式,代替函数体相对简单的函数来定义和执行

  • 不是一个代码块,仅仅是一个表达式

  • 可以有参数,甚至客户已有多个参数,但需要用逗号隔开不同的参数

下面的案例是一段很小的代码,也是一个很简短的函数:

# “小”函数举例
def printA():
  print("AAAAAAAA")

printA() #调用了函数

下面我们聊下lambda 表达是的具体用法:

  1. 以lambda开头

  2. 紧跟一定的参数(如果有的话)

  3. 参数后用冒号和表达式主题隔开

  4. 只是一个表达式,所以,没有return

具体请参照下面的案例来理解:

# 计算一个数字的100倍数
# 因为就是一个表达式,所以没有return
stm = lambda x: 100 * x
# 使用上跟函数调用一模一样
stm(89) 

或者下面也是也是一个多参数的案例:

stm2 = lambda x,y,z: x+ y*10 + z*100
stm2(4,5,6) #执行表达式

1.5.2. 高阶函数

函数的参数一般是一个值,但函数的名称同样可以作为一个值传递,把函数作为参数使用的函数,叫高阶函数

下面的案例中funA是被定义的一个函数,然后把这个函数名字作为值赋值给funB,此时funB,funA都指向了一个共同的函数体,在 调用的时候可以用任何一个名字,结果相同。

# 变量可以赋值
a = 100
b = a

# 函数名称就是一个变量
def funA():
  print("In funA")

funB = funA
funB()

以上代码得出的结论:

  • 函数名称可以作为值,也可以是变量

  • funBfunA只是名称不一样而已

既然函数名称是变量,则应该可以被当做参数传入另一个函数, 这就是所谓高阶函数的基础。

下面案例展示函数的普通调用方式,即在函数内部调用别的函数:

# 高阶函数举例
# funA是普通函数,返回一个传入数字的100倍数字
def funA(n):
  return n * 100

# 再写一个函数,把传入参数乘以300倍,
def funB(n ):
  # 最终是想返回300n
  return funA(n) * 3

print(funB(9))

上面代码是可以的,但如果想办法降低这两个函数的耦合度,就可以用高阶函数,即把函数funA作为参数传入函数funB

# 写一个高阶函数
def funC(n, f):
  # 假定函数是把n扩大100被
  return f(n) * 3

print( funC(9, funA) )

比较funCfunB, 显然funC的写法要优于funB, funC的功能如果后期面临维护或者修改,要比funB灵活许多, 例如我们需要替换funA的功能部分,根本不需要修改代码funB,只需要传入不同的函数funD即可:

  def funD(n):
    return n*10

  # 需求变更,需要把n放大三十倍,此时funB则无法实现
  print(funC(7, funD))

1.5.3. map

map原意就是映射,即把集合或者列表的元素,每一个元素都按照一定规则进行操作,结构生成一个新的列表或者集合。

Pythonmap函数是系统提供的具有映射功能的函数,返回值是一个迭代对象。

通过查询help(map)的帮助文档可以清晰的显示他的用法:

Help on class map in module builtins:

class map(object)
 |  map(func, *iterables) --> map object
 |  
 |  Make an iterator that computes the function using arguments from
 |  each of the iterables.  Stops when the shortest iterable is exhausted.

map的具体使用请参阅下面案例:

如果有一个列表,想对列表里的每一个元素乘以10并得到换一个新的列表,我们可以通过以前的知识遍历列表来完成这个功能, 例如下面代码:

l1 = [i for i in range(10)]
print(l1)
l2 = []
for i in l1:
l2.append(i * 10)

但是如果我们用map函数实现就简单的多了,先编写一个函数,这个函数是map用来映射后进行操作的函数:

# 利用map实现
def mulTen(n):
  return n*10

然我们调用map, 可以理解成用l1的每个元素逐个调用mulTen函数,然后放入结果中生成一个新的列表,虽然这种理解并不准确, 准确的说法是生成了换一个迭代器,对数据的映射操作也不是一次性完成全部,二是逐次完成:

l3 = map(mulTen, l1 )
# map类型是一个可迭代的结构,所以可以使用for遍历
for i in l3:
  print(i)

1.5.4. reduce

reduce原意是归并或者缩减,在这里代表把一个可迭代的对象归并成一个结果。

reducefunctools模块里

reduce的帮助文档如下:

reduce(...)
    reduce(function, sequence[, initial]) -> value
    
    Apply a function of two arguments cumulatively to the items of a sequence,
    from left to right, so as to reduce the sequence to a single value.
    For example, reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) calculates
    ((((1+2)+3)+4)+5).  If initial is present, it is placed before the items
    of the sequence in the calculation, and serves as a default when the
    sequence is empty.

reduce会把一个迭代对象中的内容,先把前两个元素作为才是调用某个函数,然后把函数结果作为一个参数,迭代对象 的第三个元素作为第二个参数再次调用函数,这样逐次调用迭代对象的全部参数,知道最后。

对于作为参数的函数有两个要求:

  1. 必须有两个参数

  2. 必须有返回结果

参加下面案例:

from functools import reduce

# 定义一个操作函数
# 操作函数要有两个参数和一个
def myAdd(x,y):
  return x + y

# 对于列表[1,2,3,4,5,6]执行myAdd的reduce操作
rst = reduce( myAdd, [1,2,3,4,5,6] )
print(rst)

1.5.5. filter

过滤函数,对一组数据进行过滤,符合条件的数据会生成一个新的列表并返回。

filtermap相比较:

  • 相同点:都对列表的每一个元素逐一进行操作

  • 不同:

    • map会生成一个跟原来数据相对应的新队列

    • filter不一定,只要符合条件的才会进入新的数据集合

filter函数怎么写:

  • 利用给定参数进行判断

  • 返回值一定是个布尔值

调用格式:

  • filter(f, data), f是过滤函数, data是数据

filter函数的一个案例是:

# 对于一个列表,对其进行过滤,偶数组成一个新列表

# 需要定义过滤函数, 过滤函数要求有输入,返回布尔值
def isEven(a):
   return a % 2 == 0

l = [3,4,56,3,2,3,4556,67,4,4,3,23455,43]

rst = filter(isEven, l)
# 返回的filter内容是一个可迭代对象
print(type(rst))
print(rst)

1.5.6. sorted

sorted把一个序列按照给定算法进行排序, 函数的帮助文档如下:

sorted(iterable, /, *, key=None, reverse=False)
    Return a new list containing all items from the iterable in ascending order.
    
    A custom key function can be supplied to customize the sort order, and the
    reverse flag can be set to request the result in descending order.

sorted的参数中, key参数代表的函数在排序前对每一个元素进行key函数运算,可以理解成按照key函数定义的逻辑进行排序

sorted函数在python2python3 相差巨大, 此处以python3为准

关于排序参考下面案例:

a = [234,22312,123,45,43,2,3,66723,34]
al = sorted(a, reverse=True)
print(al)

上面案例并没有时候用参数key, 下面案例将调用排序函数:

a = [-43,23,45,6,-23,2,-4345]
# 按照绝对值进行排序
# abs是求绝对值的意思
# 即按照绝对值的倒叙排列
al = sorted(a, key=abs, reverse=True)

下面案例通过调用字符串的小写转换函数,先将所有需要排序的字符串转换,然后排序:

astr = ['dana', 'Danaa', 'wangxiaojing', 'jingjing', 'haha']

str1 = sorted(astr)
print(str1)

str2 = sorted(astr, key=str.lower)
print(str2)

1.6. 装饰器

1.6.1. 函数作为返回值

函数调用可以返回具体的值,当然也可以返回一个函数作为结果。

函数作为返回值返回的时候,被返回的函数需要在函数体内定义,如下所示:

def myF2():
    def myF3():
        print("In myF3")
        return 3
    return myF3

# 使用上面定义, 调用myF2,返回一个函数myF3,赋值给f3
f3 = myF2() #得到的是函数myF3,此时f3和myF3是同一个函数
print(type(f3))
f3()

下面的案例返回一个函数,但返回函数中,使用了外层函数的某些值,这些值可能是外层函数的参数,也可能是外层函数中 定义的一些变量:

# args:参数列表
# 1 myF4定义函数,返回内部定义的函数myF5
# 2. myF5使用了外部变量,这个变量是myF4的参数
def myF4( *args):
    def myF5():
        rst = 0
        for n in args: #内部函数使用了外部函数的值
        rst += n
        return rst
    return myF5

上面函数的使用:

f5 = myF4(1,2,3,4,5,6,7,8,9,0)
# f5的调用方式
f5()

1.6.2. 闭包(closure)

当一个函数在内部又定义了一个函数,并且内部的函数使用了外部函数的参数或者局部变量,当内部函数被当做返回值的时候, 相关参数和变量保存在返回的函数中,这种结果,叫闭包

上面定义的myF4是一个标准闭包结构。

1.6.3. 闭包常见坑

闭包函数使用起来可能会遇到坑,要小心:

def count():
    # 定义列表,列表里存放的是定义的函数
    fs = []
    for i in range(1, 4):
        # 定义了一个函数f
        # f是一个闭包结构
        def f():
            return i * i
        fs.append(f)

    return fs


f1, f2, f3 = count()
print(f1())
print(f2())
print(f3())

运行结果为:

C:\Users\Augs\Anaconda3\python.exe "C:\Program Files\JetBrains\PyCharm Community Edition 2020.2.3\plugins\python-ce\helpers\pydev\pydevd.py" --multiproc --qt-support=auto --client 127.0.0.1 --port 52813 --file F:/book/python/new_python/code/te.py
pydev debugger: process 7664 is connecting

Connected to pydev debugger (build 202.7660.27)
9
9
9

Process finished with exit code 0

出现的问题分析:

  • 造成上述状况的原因是,返回函数引用了变量i, i并非立即执行,而是等到三个函数都返回的时候才统一使用,此时i已经变成了3,最终调用的时候,都返回的是 3*3

  • 此问题描述成:返回闭包时,返回函数不能引用任何循环变量

  • 解决方案: 再创建一个函数,用该函数的参数绑定循环变量的当前值,无论该循环变量以后如何改变,已经绑定的函数参数值不再改变

修改上述函数:

def count2():
    def f(j):
        def g():
            return j*j
        return g

    fs = []
    for i in range(1,4):
        fs.append(f(i))
    return fs

f1,f2,f3 = count2()
print(f1())
print(f2())
print(f3())

修改后的函数运行和能达到预期.

1.6.4. 当变量使用的函数

请看下面平淡无奇的案例:

def hello():
    print("Hello world")

hello()

函数名称可以作为变量进行赋值:

f = hello
f()

f和hello是一个函数, 这两个函数名称不同,但id相同,通过下面代码可以验证:

print(id(f))
print(id(hello))

print(f.__name__)
print(f.__name__)

现在我们有了新的需求:

  1. 对hello功能进行扩展,每次打印hello之前打印当前系统时间

  2. 而实现这个功能又不能改动现有代码

此时可以解逐装饰器来实现.

1.6.5. 装饰器(Decrator)

  • 在不改动函数代码的基础上无限制扩展函数功能的一种机制,本质上讲,装饰器是一个能够返回函数的高阶函数

  • 装饰器的使用: 使用@语法, 即在每次要扩展到函数定义前使用@+函数名

任务:

  • 对hello函数进行功能扩展,每次执行hello万打印当前时间

利用下面的代码, 及定义一个装饰器, 装饰要hello函数:

import time

# 高阶函数,以函数作为参数
def printTime(f):
    def wrapper(*args, **kwargs):
        print("Time: ", time.ctime())
        return f(*args, **kwargs)
    return wrapper

# 上面定义了装饰器,使用的时候需要用到@, 此符号是python的语法糖
@printTime
def hello():
    print("Hello world")

#利用语法糖调用
hello()

函数的运行结果如下:

C:\Users\Augs\Anaconda3\python.exe "C:\Program Files\JetBrains\PyCharm Community Edition 2020.2.3\plugins\python-ce\helpers\pydev\pydevd.py" --multiproc --qt-support=auto --client 127.0.0.1 --port 55245 --file F:/book/python/new_python/code/te.py
pydev debugger: process 12548 is connecting

Connected to pydev debugger (build 202.7660.27)
Time:  Mon Dec 27 18:34:36 2021
Hello world

Process finished with exit code 0

装饰器的好处是:一点定义,则可以装饰任意函数 而函数一旦被其装饰,则把装饰器的功能直接添加到定义函数的功能上, 但对函数原本的代码功能没有任何副作用.

参看下面代码:

@printTime
def hello2():
    print("今天很高兴,被老板揪着讲课了")
    print("还可以由很多的选择")

hello2()

上面对函数的装饰使用了系统定义的语法糖, 下面我们尝试手动执行下装饰器

我们的代码首次按使用定义好的的装饰器, 但我们手动调用这个装饰器,把相应函数传入, 调用有两种情况,一种是 调用装饰器后返回的结果放入原来被装饰的函数名:

hello3 = printTime(hello3)

另一种是返回结果单独起了一个名字:

f = printTime(hello3)

两种不同调用也引发了不同结果,思考下,为什么会这样?

import time

# 高阶函数,以函数作为参数
def printTime(f):
    def wrapper(*args, **kwargs):
        print("Time: ", time.ctime())
        return f(*args, **kwargs)
    return wrapper


def hello3():
    print("我是手动执行的")

hello3()

print("*" * 40)
hello3 = printTime(hello3)
hello3()


print("*" * 40)
f = printTime(hello3)
f()

运行结果如下:

C:\Users\Augs\Anaconda3\python.exe "C:\Program Files\JetBrains\PyCharm Community Edition 2020.2.3\plugins\python-ce\helpers\pydev\pydevd.py" --multiproc --qt-support=auto --client 127.0.0.1 --port 55607 --file F:/book/python/new_python/code/te.py
pydev debugger: process 12188 is connecting

Connected to pydev debugger (build 202.7660.27)
我是手动执行的
****************************************
Time:  Mon Dec 27 18:40:00 2021
我是手动执行的
****************************************
Time:  Mon Dec 27 18:40:00 2021
Time:  Mon Dec 27 18:40:00 2021
我是手动执行的

Process finished with exit code 0

思考题答案: 装饰器返回结果的代码ruturn f(...)是一个执行语句, 如果返回语句名称一样, 则覆盖一个, 否则会执行一个f函数一个hello3函数 再不明白可以进QQ群: 999 0977, 或者微信 13119144223

1.7. 偏函数

我们先看一个例子, 把字符串转化成十进制数字:

int("12345")

上面例子很简单,我们还可以通过这个函数求八进制的字符串12345,表示成十进制的数字是多少:

int("12345", base=8)

我们新建一个函数,此函数是默认输入的字符串是16进制数字, 把此字符串返回十进制的数字的用法是:

def int16(x, base=16):
    return int(x, base)

int16("12345")

代码很好理解,不做过多解释了.

1.7.1. 偏函数

偏函数是参数固定的函数,相当于一个由特定参数的一个函数, 实现偏函数需要用到functools.partial:

  • functools.partial的作用是,把一个函数某些函数固定,返回一个新函数

参看下面案例:

import functools
#实现上面int16的功能
int16 = functools.partial(int, base=16)

int16("12345")

1.8. 高级函数补充

1.8.1. zip

把两个可迭代内容生成一个可迭代的tuple元素类型组成的内容

zip 案例:

l1 = [ 1,2,3,4,5]
l2 = [11,22,33,44,55]

z = zip(l1, l2)

print(type(z))
print(z)

运行结果如下:

C:\Users\Augs\Anaconda3\python.exe "C:\Program Files\JetBrains\PyCharm Community Edition 2020.2.3\plugins\python-ce\helpers\pydev\pydevd.py" --multiproc --qt-support=auto --client 127.0.0.1 --port 56228 --file F:/book/python/new_python/code/te.py
pydev debugger: process 11868 is connecting

Connected to pydev debugger (build 202.7660.27)
<class 'zip'>
<zip object at 0x00000213421CED88>

Process finished with exit code 0

另一个例子:

l1 = ["wangwang", "mingyue", "yyt"]
l2 = [89, 23, 78]

z = zip(l1, l2)

for i in z:
     print(i)

运行结果如下:

C:\Users\Augs\Anaconda3\python.exe "C:\Program Files\JetBrains\PyCharm Community Edition 2020.2.3\plugins\python-ce\helpers\pydev\pydevd.py" --multiproc --qt-support=auto --client 127.0.0.1 --port 56172 --file F:/book/python/new_python/code/te.py
pydev debugger: process 12704 is connecting

Connected to pydev debugger (build 202.7660.27)
('wangwang', 89)
('mingyue', 23)
('yyt', 78)
<class 'zip'>

Process finished with exit code 0

考虑下面结果,为什么会为空

l3 = [i for i in z]
print(l3)

上面是zip的结果是一个迭代器, 迭代器运行到最后就是返回空,参看迭代器知识点

1.8.2. enumerate

  • 跟zip功能比较像

  • 对可迭代对象里的每一元素,配上一个索引,然后索引和内容构成tuple类型

enumerate案例1:

l1 = [11,22,33,44,55]

em = enumerate(l1)

l2 = [i for i in em]
print(l2)

运行结果如下:

C:\Users\Augs\Anaconda3\python.exe "C:\Program Files\JetBrains\PyCharm Community Edition 2020.2.3\plugins\python-ce\helpers\pydev\pydevd.py" --multiproc --qt-support=auto --client 127.0.0.1 --port 56285 --file F:/book/python/new_python/code/te.py
pydev debugger: process 800 is connecting

Connected to pydev debugger (build 202.7660.27)
[(0, 11), (1, 22), (2, 33), (3, 44), (4, 55)]

Process finished with exit code 0

另一个比较飘逸的例子是:

l1 = [11,22,33,44,55]
em = enumerate(l1, start=100)

l2 = [ i for i in em]
print(l2)

结果为:

C:\Users\Augs\Anaconda3\python.exe "C:\Program Files\JetBrains\PyCharm Community Edition 2020.2.3\plugins\python-ce\helpers\pydev\pydevd.py" --multiproc --qt-support=auto --client 127.0.0.1 --port 56381 --file F:/book/python/new_python/code/te.py
pydev debugger: process 13092 is connecting

Connected to pydev debugger (build 202.7660.27)
[(100, 11), (101, 22), (102, 33), (103, 44), (104, 55)]

Process finished with exit code 0

1.9. collections模块

collections 包含了一些特殊的容器,针对Python内置的容器,例如listdictsettuple,提供了另一种选择;

  • namedtuple,可以创建包含名称的tuple;

  • deque: 类似于list的容器,可以快速的在队列头部和尾部添加、删除元素;

  • Counter: dict的子类,计算可hash的对象;

  • OrderedDict: dict的子类,可以记住元素的添加顺序;

  • defaultdict: dict的子类,可以调用提供默认值的函数;

1.9.1. namedtuple

命名的元组,意味给元组中的每个位置赋予含义,意味着代码可读性更强, namedtuple可以在任何常规元素使用的地方使用,而且它可以通过名称来获取字段信息而不仅仅是通过位置索引。

  • 是一个tuple类型

  • 是一个可命名的tuple

namedtuple在给csv或者sqlite3返回的元组附上名称特别有用

请看下面例子:

>>> import collections
>>> Point = namedtuple('Point', ['x', 'y'])
>>> Point.__doc__                   # docstring for the new class
'Point(x, y)'
>>> p = Point(11, y=22)             # instantiate with positional args or keywords
>>> p[0] + p[1]                     # indexable like a plain tuple
33
>>> x, y = p                        # unpack like a regular tuple
>>> x, y
(11, 22)
>>> p.x + p.y                       # fields also accessible by name
33
>>> d = p._asdict()                 # convert to a dictionary
>>> d['x']
11
>>> Point(**d)                      # convert from a dictionary
Point(x=11, y=22)
>>> p._replace(x=100)               # _replace() is like str.replace() but targets named fields
Point(x=100, y=22)

从某种意义上说, 相当于直接创建了一个类, 这个类名称是Point, 有两个属性, x, y

下面这段代码如果不看Circle的定义, 级别就是一个定义的类而已:

Circle = collections.namedtuple("Circle", ['x', 'y', 'r'])

c = Circle(100, 150, 50)
print(c)
print(type(c))

运行结果如下:

C:\Users\Augs\Anaconda3\python.exe "C:\Program Files\JetBrains\PyCharm Community Edition 2020.2.3\plugins\python-ce\helpers\pydev\pydevd.py" --multiproc --qt-support=auto --client 127.0.0.1 --port 51985 --file F:/book/python/new_python/code/te.py
pydev debugger: process 16160 is connecting

Connected to pydev debugger (build 202.7660.27)
Circle(x=100, y=150, r=50)
<class '__main__.Circle'>

Process finished with exit code 0

想检测以下namedtuple到底属于谁的子类

isinstance(c, tuple)

运行结果是:

True

1.9.2. dequeue

deque是栈和队列的一种广义实现,是”double-end queue”的简称;

  • deque支持线程安全、有效内存地以近似o(1)的性能在deque的两端插入和删除元素

  • 尽管list也支持相似的操作,但是它主要在固定长度操作上的优化,从而在pop(0)insert(0,v)(会改变数据的位置和大小)上有o(n)的时间复杂度。

  • 比较方便的解决了频繁删除插入带来的效率问题

deque理解成列表即可, 只不过列表的追加操作比较简单,但从列表头插入数据效率比较低,deque有效解决了这个问题.

参看下面案例:

from collections import deque

q = deque(['a', 'b', 'c'])
print(q)

q.append("d")
print(q)

q.appendleft('x')
print(q)

运行结果如下:

C:\Users\Augs\Anaconda3\python.exe "C:\Program Files\JetBrains\PyCharm Community Edition 2020.2.3\plugins\python-ce\helpers\pydev\pydevd.py" --multiproc --qt-support=auto --client 127.0.0.1 --port 52828 --file F:/book/python/new_python/code/te.py
pydev debugger: process 14860 is connecting

Connected to pydev debugger (build 202.7660.27)
deque(['a', 'b', 'c'])
deque(['a', 'b', 'c', 'd'])
deque(['x', 'a', 'b', 'c', 'd'])

Process finished with exit code 0

1.9.3. defaultdict

defaultdict是内置数据类型dict的一个子类,基本功能与dict一样,只是重写了一个方法__missing__(key)和 增加了一个可写的对象变量default_factory

当直接读取dict不存在的属性时,直接返回默认值

看一个例子:

d1 = {"one":1, "two":2, "three":3}
print(d1['one'])
print(d1['four'])

上面代码会出一个语法错误, 点解?

而如果用defaultdict则不会报错,参看下面代码:

from collections import defaultdict
# lambda表达式,直接返回字符串
func = lambda: "刘大拿"
d2 = defaultdict(func)

d2["one"] = 1
d2["two"] = 2

print(d2['one'])
print(d2['four'])

运行结果如下:

C:\Users\Augs\Anaconda3\python.exe "C:\Program Files\JetBrains\PyCharm Community Edition 2020.2.3\plugins\python-ce\helpers\pydev\pydevd.py" --multiproc --qt-support=auto --client 127.0.0.1 --port 53018 --file F:/book/python/new_python/code/te.py
pydev debugger: process 15108 is connecting

Connected to pydev debugger (build 202.7660.27)
1
刘大拿

Process finished with exit code 0

再打印d2["four"]的时候, 因为没有这个值,最终调用func函数,返回字符串刘大拿`.

1.9.4. Counter

Counter统计字符串个可以支持方便、快速的计数数

例子:

from collections import Counter

# 为什么下面结果不把abcdefgabced.....作为键值,而是以其中每一个字母作为键值
# 需要括号里内容为可迭代
c = Counter("abcdefgabcdeabcdabcaba")
print(c)


s = ["liudana", "love", "love", "love", "love", "wangxiaona"]
c = Counter(s)

print(c)

运行结果如下:

C:\Users\Augs\Anaconda3\python.exe "C:\Program Files\JetBrains\PyCharm Community Edition 2020.2.3\plugins\python-ce\helpers\pydev\pydevd.py" --multiproc --qt-support=auto --client 127.0.0.1 --port 53101 --file F:/book/python/new_python/code/te.py
pydev debugger: process 3560 is connecting

Connected to pydev debugger (build 202.7660.27)
Counter({'a': 6, 'b': 5, 'c': 4, 'd': 3, 'e': 2, 'f': 1, 'g': 1})
Counter({'love': 4, 'liudana': 1, 'wangxiaona': 1})

Process finished with exit code 0

1.9.5. OrderedDict

  • OrderedDict类似于正常的词典,当在有序的词典上迭代时,返回的元素就是它们第一次添加的顺序, 及有序字典中的顺序指的是插入顺序

  • class collections.OrderedDict,返回已给dict的子类,支持常规的dict的方法

  • OrderedDict是一个记住元素首次插入顺序的词典,如果一个元素重写已经存在的元素,那么原始的插入位置保持不变,如果删除一个元素再重新插入,那么它就在末尾

  • OrderedDict.popitem(last=True)中, popitem方法返回和删除一个(key,value)对,如果last=True,就以LIFO方式执行,否则以FIFO方式执行。

  • OrderedDict也支持反向迭代,例如reversed()

  • OrderedDict对象之间的相等测试,例如list(od1.items()) == list(od2.items()),是对顺序敏感的

  • OrderedDict和其他的映射对象(例如常规的词典)之间的相等测试是顺序不敏感的,这就允许OrderedDict对象可以在使用常规词典的地方替换掉常规词典。

  • OrderedDict构造器和update()方法可以接受关键字变量,但是它们丢失了顺序,因为Python的函数调用机制是将一个无序的词典传入关键字变量

  • 一个有序的词典记住它的成员插入的顺序,可以使用排序函数,将其变为排序的词典

参看案例如下:

from collections import OrderedDict

a = {"banana":3,"apple":2,"pear":1,"orange":4}
print(a)
# dict sorted by key, 每个 (k,v)中按 k排序
b = OrderedDict(sorted(a.items(),key = lambda t:t[0]))
print(b)

# dict sorted by value
# 每个 (k,v)中按 v排序
c = OrderedDict(sorted(a.items(),key = lambda t:t[1]))
print(c)

# 按k的长度排序
d =  OrderedDict(sorted(a.items(),key = lambda t:len(t[0])))
del d['apple']
print(d)

d["apple"] = 2
print(d)

运行结果如下:

C:\Users\Augs\Anaconda3\python.exe "C:\Program Files\JetBrains\PyCharm Community Edition 2020.2.3\plugins\python-ce\helpers\pydev\pydevd.py" --multiproc --qt-support=auto --client 127.0.0.1 --port 55485 --file F:/book/python/new_python/code/te.py
pydev debugger: process 16244 is connecting

Connected to pydev debugger (build 202.7660.27)
{'banana': 3, 'apple': 2, 'pear': 1, 'orange': 4}
OrderedDict([('apple', 2), ('banana', 3), ('orange', 4), ('pear', 1)])
OrderedDict([('pear', 1), ('apple', 2), ('banana', 3), ('orange', 4)])
OrderedDict([('pear', 1), ('banana', 3), ('orange', 4)])
OrderedDict([('pear', 1), ('banana', 3), ('orange', 4), ('apple', 2)])

Process finished with exit code 0